This is a bqplot
recreation of Mike Bostock's Wealth of Nations. This was also done by Gapminder. It is originally based on a TED Talk by Hans Rosling.
In [ ]:
# Required imports
import pandas as pd
from bqplot import (LogScale, LinearScale, OrdinalColorScale, ColorAxis,
Axis, Scatter, CATEGORY10, Label, Figure)
from bqplot.default_tooltip import Tooltip
from ipywidgets import VBox, IntSlider, Button
from IPython.display import display
import os
import numpy as np
from time import sleep
In [ ]:
# The GUI starts with this year
initial_year = 1800
In [ ]:
data = pd.read_json(os.path.abspath('data_files/nations.json'))
In [ ]:
def clean_data(data):
for column in ['income', 'lifeExpectancy', 'population']:
data = data.drop(data[data[column].apply(len) <= 4].index)
return data
def extrap_interp(data):
data = np.array(data)
x_range = np.arange(1800, 2009, 1.)
y_range = np.interp(x_range, data[:, 0], data[:, 1])
return y_range
def extrap_data(data):
for column in ['income', 'lifeExpectancy', 'population']:
data[column] = data[column].apply(extrap_interp)
return data
In [ ]:
data = clean_data(data)
data = extrap_data(data)
In [ ]:
income_min, income_max = np.min(data['income'].apply(np.min)), np.max(data['income'].apply(np.max))
life_exp_min, life_exp_max = np.min(data['lifeExpectancy'].apply(np.min)), np.max(data['lifeExpectancy'].apply(np.max))
pop_min, pop_max = np.min(data['population'].apply(np.min)), np.max(data['population'].apply(np.max))
In [ ]:
def get_data(year):
year_index = year - 1800
income = data['income'].apply(lambda x: x[year_index])
life_exp = data['lifeExpectancy'].apply(lambda x: x[year_index])
pop = data['population'].apply(lambda x: x[year_index])
return income, life_exp, pop
In [ ]:
tt = Tooltip(fields=['name', 'x', 'y'], labels=['Country Name', 'Income per Capita', 'Life Expectancy'])
In [ ]:
year_label = Label(x=0.85, y=0.1, font_size='52px', font_weight='bolder', color='orange',
text=str(initial_year), enable_move=True)
In [ ]:
x_sc = LogScale(min=income_min, max=income_max)
y_sc = LinearScale(min=life_exp_min, max=life_exp_max)
c_sc = OrdinalColorScale(domain=data['region'].unique().tolist(), colors=CATEGORY10[:6])
size_sc = LinearScale(min=pop_min, max=pop_max)
In [ ]:
ax_y = Axis(label='Life Expectancy', scale=y_sc, orientation='vertical', side='left')
ax_x = Axis(label='Income per Capita', scale=x_sc)
In [ ]:
# Start with the first year's data
cap_income, life_exp, pop = get_data(initial_year)
In [ ]:
wealth_scat = Scatter(x=cap_income, y=life_exp, color=data['region'], size=pop,
names=data['name'], display_names=False,
scales={'x': x_sc, 'y': y_sc, 'color': c_sc, 'size': size_sc},
default_size=4112, tooltip=tt, animate=True, stroke='Black')
In [ ]:
fig = Figure(marks=[wealth_scat, year_label], axes=[ax_x, ax_y],
title='Health and Wealth of Nations', fig_color='White',
animation_duration=100)
In [ ]:
year_slider = IntSlider(min=1800, max=2008, step=1, description='Year', value=initial_year)
In [ ]:
animate_button = Button(description='Play', background_color='MediumSeaGreen', color='Black', icon='fa-play')
On the slider value callback
(a function that is triggered everytime the value
of the slider is changed) we change the x
, y
and size
co-ordinates of the Scatter
. We also update the text
of the Label
to reflect the current year.
In [ ]:
def year_changed(new):
wealth_scat.x, wealth_scat.y, wealth_scat.size = get_data(year_slider.value)
year_label.text = str(year_slider.value)
year_slider.observe(year_changed, 'value')
In [ ]:
def button_clicked(value):
animate_button.visible = False
for i in range(1800, 2009, 1):
year_slider.value = i
sleep(0.05)
animate_button.visible = True
animate_button.on_click(button_clicked)
In [ ]:
display(VBox([animate_button, fig, year_slider]))